package com.jmxcfc.blfsc.ssq.service.contract.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jmxcfc.blfsc.common.common.enums.BaseErrorEnum;
import com.jmxcfc.blfsc.common.common.enums.ProcessStateEnum;
import com.jmxcfc.blfsc.common.common.request.BaseRequest;
import com.jmxcfc.blfsc.common.common.response.BaseResponse;
import com.jmxcfc.blfsc.common.exception.AfterOutboundException;
import com.jmxcfc.blfsc.common.exception.BeforeOutboundException;
import com.jmxcfc.blfsc.common.exception.OutboundException;
import com.jmxcfc.blfsc.common.exception.ValidBodyException;
import com.jmxcfc.blfsc.common.sign.SignRequest;
import com.jmxcfc.blfsc.common.sign.enums.SignType;
import com.jmxcfc.blfsc.common.sign.extend.SignExtend;
import com.jmxcfc.blfsc.common.sms.BaseContext;
import com.jmxcfc.blfsc.common.validation.rule.RequestFieldRule;
import com.jmxcfc.blfsc.common.validation.rule.SignRulesUtil;
import com.jmxcfc.blfsc.ssq.common.Constants;
import com.jmxcfc.blfsc.ssq.entity.SignContractSub;
import com.jmxcfc.blfsc.ssq.modle.response.SsqResponse;
import com.jmxcfc.blfsc.ssq.service.SignatureService;
import com.jmxcfc.blfsc.ssq.service.contract.ISignContractRequestService;
import com.jmxcfc.blfsc.ssq.service.contract.ISignContractSubService;
import com.jmxcfc.blfsc.ssq.service.contract.IUploadContractSubService;
import com.jmxcfc.blfsc.ssq.util.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 功能描述
 * 签署合同
 * @author: pbs
 * @date: 2024年09月04日 0:12
 */
@Service
@Slf4j
public class SignContractRequestServiceImpl implements ISignContractRequestService {


    @Value("${com.ssq.signContractUrl}")
    private String signContractUrl;



    private SignatureService signatureService;

    private IUploadContractSubService uploadContractSubService;
    private ISignContractSubService signContractSubService;
    private RestTemplate restTemplate;

    @Autowired
    public void setSignatureService(SignatureService signatureService) {
        this.signatureService = signatureService;
    }

    @Autowired
    public void setUploadContractSubService(IUploadContractSubService uploadContractSubService) {
        this.uploadContractSubService = uploadContractSubService;
    }

    @Autowired
    public void setSignContractSubService(ISignContractSubService signContractSubService) {
        this.signContractSubService = signContractSubService;
    }


    @Autowired
    public void setRestTemplate(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    /**
     * 1、参数校验
     * 头部校验:requestNo(业务号)
     * 都校验:signType(签署类型)
     * 个人章校验: 账号(取值证件号idCode)、signX、signY、signPage
     * 企业章校验: 账号(sealAccount(企业章账号))、signXEnt、signYEnt、signPageEnt
     * 2、根据signType判断需要调用接口几次,signType=1(个人+企业)则需要调用两次接口【两次 签署的账号不一样】,
     signType=2或signType=3只需要调用一次传输对应的签署账号
     * signType=1调用流程:
     (1)先查询个人签章:根据业务号、合同id、签署账号(个人)是否签署过,未签署过则用该账号进行签署,
     如果签成功【ProcessStateEnum.SUCCESS != response.getHeader().getProcessState()】则进行企业章签署,签署失败则直接返回blfs
     (2)企业章:根据业务号、合同id、签署账号(企业)是否签署过,未签署过则用该账号进行签署,响应blfs最后一次调用的结果
     (3)如果SSQ_SIGN_CONTRACT_SUB数据库存在 个人章、企业章都成功记录则响应 blfs为成功
     *
     * 2、特别注意:
     在sendRequest 方法:   baseContext.setExtendStr 【签章账号&上合同id】 =>传递到buildParam方法参数组织方法
     在参数组织完成后在 beforeOutbound 方法模块塞 由于是签名参数追加为url参数:/openapi/v2/contract/sign/cert/?developerId=aaaaaa&rtick=14840209310001&signType=rsa
     =》传递到 outbound 阶段
     * @param requestMessage
     * @return
     */
    @Override
    public BaseResponse<SignContractSub> sendRequest(BaseRequest<SignRequest<SignExtend>> requestMessage) {
        BaseContext<String> baseContext = new BaseContext<>();
        //头部参数校验规则:业务号
        List<RequestFieldRule<?>> headerElementRules = baseContext.getHeaderElementRules();
        headerElementRules.add(SignRulesUtil.getRequestNoRule());

        //请求体body参数校验: 账号(证件号)
        List<RequestFieldRule<?>> bodyElementRules = baseContext.getBodyElementRules();
        //bodyElementRules.add(SignRulesUtil.getTemplePdfPathRule());

        //body的扩展字段参数校验
        List<RequestFieldRule<?>> bodyExtendElementRules = baseContext.getBodyExtendElementRules();
        //签署类型校验,(1:签署个人签名企业签名、2:只签署企业签名、3:只签署个人签名)
        bodyExtendElementRules.add(SignRulesUtil.getSignTypeRule());


        String requestNo = requestMessage.getHeader().getRequestNo();

        //扩展字段:签署类型(1:签署个人签名企业签名、2:只签署企业签名、3:只签署个人签名)
        SignType signType = SignType.getEnumByType(requestMessage.getBody().getExtend().getSignType());

        String contractId = uploadContractSubService.queryLatestContractId(requestNo);
        log.info("根据请求业务号requestNo:{},【签署合同接口】查询表SSQ_UPLOAD_CONTRACT_SUB,获取到最新一条记录的合同id(contractId):{}", requestNo, contractId);
        if (StringUtils.isBlank(contractId)) {
            log.error("根据请求业务号requestNo:{},查询合同上传接口表: SSQ_UPLOAD_CONTRACT_SUB,未查询到的合同ID(contractId)数据,签署合同接口结束,不进行后续处理", requestNo);
            return BaseResponse.fail(ProcessStateEnum.FAILURE, BaseErrorEnum.OK, "根据业务号,未查询到合同上传接口响应的的合同ID(contractId)数据");
        }

        String idCode = requestMessage.getBody().getIdCode();
        String sealAccount = requestMessage.getBody().getExtend().getSealAccount();
        BaseResponse<SignContractSub> response = new BaseResponse<>(BaseErrorEnum.START);
        log.info("请求业务号requestNo:{},签署类型:{}({}),获取到合同id:{},进入签署操作入口", requestNo, signType.type(), signType.desc(), contractId);
        switch (signType) {
            case TYPE_1:
                //签署个人签名企业签名
                log.info("请求业务号requestNo:{},签署类型:{}({}),需要进行两次签署(个人+企业)", requestNo, signType.type(), signType.desc());
                //特殊处理:将:个人章账号&上合同id 传递到后续流程
                boolean executeFlag = Boolean.FALSE;
                if (StringUtils.isNotBlank(idCode)&&signContractSubService.notExistSignContract(requestNo, idCode, contractId)) {
                    //个人章账号&上合同id
                    baseContext.setExtendStr(StringUtils.joinWith(Constants.AND, idCode, contractId));
                    //新增个人章相关规则
                    getSignType3BodyExtendRules(bodyElementRules,bodyExtendElementRules);
                    log.info("请求业务号requestNo:{},进行签署类型:{}({}),需要进行两次签署(个人+企业),进行【签署个人章签署】,【个人章账号&上合同id】={},开始签署流程",requestNo, signType.type(), signType.desc(),baseContext.getExtendStr());
                    response= execute(requestMessage, baseContext);
                    executeFlag = Boolean.TRUE;
                    log.info("请求业务号requestNo:{},进行签署类型:{}({}),需要进行两次签署(个人+企业),进行【签署个人章签署】,响应结果:{}", requestNo, signType.type(), signType.desc(), JSON.toJSONString(response));
                    if (ProcessStateEnum.SUCCESS != response.getHeader().getProcessState()) {
                        log.error("请求业务号requestNo:{},进行签署个人章流程响应非成功,结束后续流程处理", requestNo);
                        return response;
                    }
                }

                if (StringUtils.isNotBlank(sealAccount)&&signContractSubService.notExistSignContract(requestNo, sealAccount, contractId)) {
                    //企业章账号&上合同id
                    baseContext.setExtendStr(StringUtils.joinWith(Constants.AND,sealAccount, contractId));
                    //新增个人章相关规则
                    getSignType2BodyExtendRules(bodyExtendElementRules);
                    log.info("请求业务号requestNo:{},进行签署类型:{}({}),需要进行两次签署(个人+企业),进行【签署企业章签署】,【企业章账号&上合同id】={},开始签署流程",requestNo, signType.type(), signType.desc(),baseContext.getExtendStr());
                    response = execute(requestMessage, baseContext);
                    executeFlag = Boolean.TRUE;
                    log.info("请求业务号requestNo:{},进行签署类型:{}({}),需要进行两次签署(个人+企业),进行【签署企业章签署】,响应结果:{}", requestNo, signType.type(), signType.desc(), JSON.toJSONString(response));
                }


                if (!executeFlag) {
                    //executeFlag为false 则说明在存在个人章签署成功记录、存在企业章成功的记录
                    log.warn("请求业务号requestNo:{},进行签署类型:{}({}),个人章和企业章都有存在签署成功记录,响应成功给blfs",requestNo,signType.type(),signType.desc());
                    response.getHeader().setProcessState(ProcessStateEnum.SUCCESS);
                    response.getHeader().setResCode("");
                    response.getHeader().setResMessage("个人章和企业章都有存在签署成功记录");
                }

                break;
            case TYPE_2:
                //只签署企业签名场景
                //扩展字段:企业章章相关校验
                baseContext.setExtendStr(StringUtils.joinWith(Constants.AND,sealAccount, contractId));
                //获取规则校验
                getSignType2BodyExtendRules(bodyExtendElementRules);
                log.info("请求业务号requestNo:{},进行签署类型:{}({}),签署账号&上合同id:{}", requestNo,SignType.TYPE_2.type(),SignType.TYPE_2.desc(), baseContext.getExtendStr());
                response = execute(requestMessage, baseContext);
                break;
            case TYPE_3:
                //只签署个人签名场景
                baseContext.setExtendStr(StringUtils.joinWith(Constants.AND, idCode, contractId));
                //扩展字段:个人章相关校验
                getSignType3BodyExtendRules(bodyElementRules,bodyExtendElementRules);
                log.info("请求业务号requestNo:{},进行签署类型:{}({}),签署账号&上合同id:{}", requestNo,SignType.TYPE_3.type(),SignType.TYPE_3.desc(), baseContext.getExtendStr());
                response = execute(requestMessage, baseContext);
                break;
        }
        return response;
    }

    /**
     *企业章相关校验规则:企业章的账户、签章签署页码位置(企业章)、x轴(企业章)、y轴(企业章)
     * @param bodyExtendElementRules
     */
    private void getSignType2BodyExtendRules(List<RequestFieldRule<?>> bodyExtendElementRules){
        //账号:取值于扩展字段的企业章账号
        bodyExtendElementRules.add(SignRulesUtil.getSealAccountRule());
        bodyExtendElementRules.add(SignRulesUtil.getSignPageEntRule());
        bodyExtendElementRules.add(SignRulesUtil.getSignXEntRule());
        bodyExtendElementRules.add(SignRulesUtil.getSignYEntRule());
    }

    /**
     * 个人章相关校验规则: 证件号(账号)、签章签署页码位置(个人章)、x轴(个人章)、y轴(个人章)
     *
     * @param bodyExtendElementRules
     */
    private void getSignType3BodyExtendRules(List<RequestFieldRule<?>> bodyElementRules, List<RequestFieldRule<?>> bodyExtendElementRules) {
        //账号(取值于客户的证件号)
        bodyElementRules.add(SignRulesUtil.getIdCodeRule());

        //扩展字段
        bodyExtendElementRules.add(SignRulesUtil.getSignPageRule());
        bodyExtendElementRules.add(SignRulesUtil.getSignXRule());
        bodyExtendElementRules.add(SignRulesUtil.getSignYRule());
    }


    /**
     * 重写扩展字段的校验:
     * @param body (不能传JSONObject)
     *备注:每个实现类需要自己实现该方法,然后进行扩展字段的校验
     * @param baseHandleContext
     * @param <E>
     * @throws ValidBodyException
     */
    @Override
    public <E> void validBodyExtend(SignRequest<SignExtend> body, BaseContext<E> baseHandleContext) throws ValidBodyException {
        try {
            if (Objects.nonNull(baseHandleContext)) {
                baseHandleContext.getBodyExtendElementRules().forEach(rule -> valid(rule, body.getExtend()));
            }
        } catch (RuntimeException e) {
            throw new ValidBodyException(e.getMessage());
        }
    }

    /**
     * 1、规则校验:校验接口必传参数
     * 2、参数组织: contractId取值于 签署合同接口响应的合同id[contractId]字段,取最新一条
     * @param request
     * @param response
     * @param baseContext
     * @throws BeforeOutboundException
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void beforeOutbound(BaseRequest<SignRequest<SignExtend>> request, BaseResponse<SignContractSub> response, BaseContext<String> baseContext) throws BeforeOutboundException {
        String requestNo = request.getHeader().getRequestNo();
        try {
            log.info("请求业务号requestNo:{},签署合同接口,前置处理:数据落库和参数组装", requestNo);
            SignContractSub signContractSub = new SignContractSub();
            signContractSub.setRequestNo(requestNo);
            signContractSub.setSignType(request.getBody().getExtend().getSignType());

            //参数组织、regUserPersonalSub设置值
            String requestParamStr = buildParam(request, signContractSub, baseContext);

            //由于是签名参数追加为url参数:/openapi/v2/contract/sign/cert/?developerId=aaaaaa&rtick=14840209310001&signType=rsa
            String urlPathParams = signatureService.buildUrlPathParams(signContractUrl, "", requestParamStr);
            //请求路径的url的参数
            baseContext.setExtendStr(urlPathParams);
            //设置请求
            baseContext.setRestHttpEntity(HttpUtils.creatRequestEntity(requestParamStr));
            //保存数据
            signContractSubService.save(signContractSub);
            //主键
            baseContext.setRecordId(signContractSub.getId());
            log.info("请求业务号requestNo:{},签署合同接口,前置处理结束", requestNo);
        } catch (Exception e) {
            log.error("请求业务号requestNo:{},前置处理异常:{}", requestNo, e.getMessage(), e);
            throw new BeforeOutboundException(e.getMessage(), e);
        }
    }

    @Override
    public void outbound(BaseRequest<SignRequest<SignExtend>> request, BaseResponse<SignContractSub> response, BaseContext<String> baseContext) throws OutboundException {
        try{
            //请求url的,必须要用URL,否则restTemplate会对url进行
            URI requestUrl = new URI( StringUtils.join(signContractUrl, baseContext.getExtendStr()));
            log.info("请求业务号requestNo:{},签署合同接口,发送请求外呼,请求地址:{},请参数:{}",request.getHeader().getRequestNo(),requestUrl,baseContext.getRestHttpEntity().getBody());
            String resp = restTemplate.postForObject(requestUrl, baseContext.getRestHttpEntity(), String.class);
            log.info("请求业务号requestNo:{},签署合同接口,发送请求外呼,响应内容:{}", request.getHeader().getRequestNo(), resp);
            baseContext.setRestResponse(resp);
        } catch (Exception e) {
            log.error("请求业务号requestNo:{},签署合同接口,发送请求外呼异常:{}", request.getHeader().getRequestNo(), e.getMessage());
            throw new OutboundException(e.getMessage(), e);
        }
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void afterOutbound(BaseRequest<SignRequest<SignExtend>> request, BaseResponse<SignContractSub> response, BaseContext<String> baseContext) throws AfterOutboundException {
        String requestNo = request.getHeader().getRequestNo();
        try{
            log.info("请求业务号requestNo:{},签署合同接口,后置处理:解析响应结果", requestNo);
            SignContractSub signContractSub = new SignContractSub();
            signContractSub.setRequestNo(requestNo);
            //注意这边是根据主键更新
            signContractSub.setId(baseContext.getRecordId());

            SsqResponse ssqResponse = JSON.parseObject(baseContext.getRestResponse(),SsqResponse.class);
            //错误码值,0:成功
            String errno = ssqResponse.getErrno();
            //错误信息描述
            String errmsg = ssqResponse.getErrmsg();
            log.info("请求业务号requestNo:{},签署合同接口,响应的结果 errno:{},errmsg:{}", requestNo, errno, errmsg);
            if (StringUtils.equals(errno, Constants.ERRNO_0)) {
                response.getHeader().setProcessState(ProcessStateEnum.SUCCESS);
            }else {
                response.getHeader().setProcessState(ProcessStateEnum.FAILURE);
            }
            signContractSub.setErrno(errno);
            signContractSub.setErrmsg(errmsg);
            signContractSubService.updateById(signContractSub);
            response.getHeader().setResCode(errno);
            response.getHeader().setResMessage(errmsg);
            response.setBody(signContractSub);
            log.info("请求业务号requestNo:{},签署合同接口接口,后置处理结束", requestNo);
        } catch (Exception e) {
            log.error("请求业务号requestNo:{},签署合同接口接口,后置处理异常:{}", requestNo, e.getMessage(), e);
            throw new AfterOutboundException(e.getMessage(), e);
        }
    }


    /**
     * 参数组织
     * 1、baseContext.getExtendStr() 在 sendRequest 方法塞了 签署账号&合同id
     * @param request
     * @param signContractSub
     * @param baseContext
     * @return
     */
    private String buildParam(BaseRequest<SignRequest<SignExtend>> request, SignContractSub signContractSub,BaseContext<String> baseContext) {
        String requestNo = request.getHeader().getRequestNo();
        log.info("请求业务号requestNo:{},进行参数组装,请求参数:{}", requestNo, JSON.toJSONString(request));
        SignType signType = SignType.getEnumByType(request.getBody().getExtend().getSignType());
        //签署账号&合同id
        String[] extendStrSplit = StringUtils.split(baseContext.getExtendStr(), Constants.AND);
        //在sendRequest方法进行查询和组装过签署账号和合同id
        String signAccount = extendStrSplit[0];
        String contractId = extendStrSplit[1];

        log.info("请求业务号requestNo:{},签署类型: {}({}),签署合同Id(contractId){},签署账号:{},进行请求参数组装", requestNo, signType.type(), signType.desc(), contractId, signAccount);
        JSONObject requestParam = new JSONObject();
        SignRequest<SignExtend> signRequestBody = request.getBody();
        //签名位置数组字段
        List<JSONObject> signaturePositions = new ArrayList<>();
        //签名位置
        JSONObject signaturePosition = new JSONObject();

        //signType=1(签署个人签名企业签名)时:根据业务号、合同id=>先查询企业章是否签署过,未签署过则先签署企业章、签署过则签署个人章
        switch (signType) {
            case TYPE_1:
                if (StringUtils.equals(signAccount, signRequestBody.getIdCode())) {
                    log.info("请求业务号requestNo:{},签署类型:{}({}),进行个人章签署相关参数组织",requestNo,signType.type(),signType.desc());
                    signContractSub.setSignDesc("签署个人签名企业签名-个人章签署");
                    signaturePosition = buildSignaturePosition(signRequestBody);
                }else {
                    log.info("请求业务号requestNo:{},签署类型:{}({}),进行企业章签署相关参数组织",requestNo,signType.type(),signType.desc());
                    signContractSub.setSignDesc("签署个人签名企业签名-企业章签署");
                    signaturePosition =  buildSignaturePositionEnt(signRequestBody);
                }
                break;
            case TYPE_2:
                //只签署企业签名
                signContractSub.setSignDesc(SignType.TYPE_2.desc());
                signaturePosition = buildSignaturePositionEnt(signRequestBody);
                break;

            case TYPE_3:
                //只签署个人签名
                signContractSub.setSignDesc(SignType.TYPE_3.desc());
                signaturePosition = buildSignaturePosition(signRequestBody);
                break;
        }
        //签署账号
        requestParam.put(Constants.SIGNER_ACCOUNT, signAccount);
        //签署合同id
        requestParam.put(Constants.CONTRACT_ID, contractId);


        //签章位置
        signaturePositions.add(signaturePosition);
        requestParam.put(Constants.SIGNATURE_POSITIONS, signaturePositions);

        signContractSub.setSignerAccount(requestParam.getString(Constants.SIGNER_ACCOUNT));
        signContractSub.setContractId(contractId);
        signContractSub.setPageNum(signaturePosition.getString(Constants.PAGE_NUM));
        signContractSub.setX(signaturePosition.getString("x"));
        signContractSub.setY(signaturePosition.getString("y"));
        log.info("请求业务号requestNo:{},参数组织完成,参数:{}", request.getHeader().getRequestNo(), JSON.toJSONString(requestParam));
        return JSON.toJSONString(requestParam);
    }

    /***
     * 个人章位置
     * @param signRequestBody
     * @return  返回个人章签名位置
     */
    private JSONObject  buildSignaturePosition(SignRequest<SignExtend> signRequestBody){
        JSONObject signaturePosition = new JSONObject();
        //签名页码、
        signaturePosition.put(Constants.PAGE_NUM, signRequestBody.getExtend().getSignPage());
        //x轴
        signaturePosition.put("x", signRequestBody.getExtend().getSignX());
        //y轴
        signaturePosition.put("y", signRequestBody.getExtend().getSignY());
        //返回个人章签名位置
        return signaturePosition;
    }


    /**
     * 企业章签名位置
     * @param signRequestBody
     * @return
     */
    private JSONObject buildSignaturePositionEnt( SignRequest<SignExtend> signRequestBody){
        JSONObject signaturePosition = new JSONObject();
        //签名页码、
        signaturePosition.put(Constants.PAGE_NUM, signRequestBody.getExtend().getSignPageEnt());
        //x轴
        signaturePosition.put("x", signRequestBody.getExtend().getSignXEnt());
        //y轴
        signaturePosition.put("y", signRequestBody.getExtend().getSignYEnt());
        return signaturePosition;
    }


}